Xposome is a shiny application that aims to build a repository of gene expression datasets that profile the transcriptomic response to exposure of known toxins.
Must have R and Rstudio installed before proceeding:
To run Xposome application locally:
Not familiar with Docker? You can download it here and check out the R Docker tutorials.
To dockerize the Xposome application:
The docker image for Xposome is also available to download at Docker Hub. To pull the image, just type the following command in the terminal:
Additionally, check out how to configure http or https web posting with NGINX and Docker-Compose
The Xposome application requires several structural datasets in order for it to run smoothly. We provide an API access to our data, see API Explorer.
There are two main parts of the Xposome application:
Contains a list of chemical screenings that exposed to high-throughput transcriptomics assays. Each screen provides information about the chemicals and cell-line that was used in the experiment. A detailed analysis was done to explore the gene expression set, gene set enrichment, and connectivity map of interested cell-line that exposed to a panel of known carcinogens.

Below is a snapshot of the Adipogenicity portal:

Requires access from authorized users. This restricted page only allows authorized users to make changes to the existed screens or to upload new screens to the portal page.
The default login credentials are set as:
Username: Xposome
Password: Xposome
Lastly, check out the documentation of how to reset password if users forgot their login credentials with Postfix

Developed by Reina Chau, Stefano Monti.
Site built with flexdashboard 0.5.2
NGINX - An open source web server and reverse proxy technology used for hosting websites and applications
Docker-compose - A technology for enabling docker containers to communicate to each other
In this post, I will go over steps of how to create an unencrypt (http) or encrypt (https) traffic that allows users to interact with the Xposome application that is published through a docker container. To achieve that, we need both NGINX and docker-compose.
Here are the seven steps to set-up NGINX and docker-compose:
See how to install Docker-compose here
To check if docker-compose is installed,
docker-compose --version
An image for NGINX can be found at Docker Hub. You can pull this image without the need to install the NGINX software locally,
docker pull nginx:latest
To check if the image is built successfully,
docker images
Also see this link on how to use the image
Check out this post for more details
There are two configuration files for NGINX, nginx.conf and default.conf.template files.
The nginx.conf file contains the standard configuration for NGINX. Unless you know how to add directives specified for a configuration file, otherwise, I do not recommend making any changes to this file.
The default.conf.templat file contains configuration that allows NGINX to direct any encrypt or unencrypt traffics to an application that is published on a specific port on the host machine.
How NGINX talks to Docker
With NGINX,
nginx → listens on → port 80 (http) and 443 (https)
With Docker,
Docker → listens on → port 80/443/8080/3838/8000/…
With SHINY
Shiny → listens on localhost → port 3838/8787/4848/…
In other words, when we run a docker container, we basically expose our application through a port that can be viewable thru a Shiny's localhost. Docker listens to this port and will publish the application to a specified port on the host machine (for example: port 7856 for simplicity). Therefore, when we navigate to port 7856 on the host domain (for example http://[domain_name/ip address]:7856), we will see that the shiny app is hosted there.
Nevertheless, we probably don't want to navigate to the application via a port link. Thus, we can use the NGINX configuration files to create an alias location for the application (for example http://[domain_name/ip address]/Xposome/). When a user is navigating to that specific address on the host domain, NGINX will transfer that traffic to the port 7856 which the application was originally published on. As the result, NGINX is served as a reverse proxy for hosting our applications.
Here is a snapshot of how the http configuration file looks like:
server {
listen 80;
server_name 155.41.202.164; #ip address or domain name
server_tokens off;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
# Specific the location for the xposome app
location /Xposome/ {
rewrite ^/Xposome/(.*)$ /$1 break;
proxy_pass http://example.com:7856/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 20d;
proxy_buffering off;
}
}
Once we had configured NGINX as a reverse proxy for our application, we can create a docker-compose.yml file that comprises of all the containers that we want to communicate with the NGINX container.
To enable communication between containers, in the docker-compose file, we must create a service to run the NGINX container and a service to run the container for the Xposome application. Under each service, we need to specify a list of instructions of how the container can be built, for instance, what image is used to build the container, what do we want to name the container, what port is used to expose the shiny app to Docker, and what port is used to publish the app onto the host machine.
After we had defined all of these keys components of how the containers are built, in the NGINX configuration files, we need to make sure that the host port matches the port that NGINX redirect the encrypt or unencrypt traffic to the application.
Here is a snapshot of the docker-compose.yml file:
version: '3'
services:
nginx:
image: nginx:latest #name of the image
container_name: nginx #name of the container you want to build
restart: always #allow the connection to restart if it gets disconnected
ports:
- 80:80 #specify the unencrypt port
- 443:443 #specify the encrypt port but will need ssl later
volumes:
- /home/docker/nginx/nginx.conf:/etc/nginx/nginx.conf #connect the host configuration files to docker config files
- /home/docker/nginx/conf.d:/etc/nginx/templates #connect the host configuration files to docker config files
command: [nginx-debug, '-g', 'daemon off;']
xposome:
image: montibot/xposome:latest #name of the docker image
container_name: xposome #name of the container
restart: always
ports:
- 7856:7856 #port where the application is published on
volumes:
- /home/xposome/shinyApps/:/srv/shiny-server/ #location to the codebse or data files
- /home/xposome/shinyApps-log/:/var/log/shiny-server/ #location to store log information from the shiny app
After we have the docker-compose file and NGINX configuration files all set up, we can cd to where docker-compose.yml is located and run the following command on the terminal.
docker-compose up -d
-d is used to run docker compose in detach mode
Navigate to the pre-specified domain (for example http://155.41.202.164/Xposome/) on host server and see if the application is hosted there.
Developed by Reina Chau, Stefano Monti.
Site built with flexdashboard 0.5.2
Postfix - A popular open-source Mail Transfer Agent (MTA) that can be used to route and deliver emails on a Linux system
Docker - A software platform that allows developers to build, test, deploy, and run applications using containers
In this post, I will go over steps of how to send emails from a docker container through Postfix installed on the host machine. In my previous post, we have learned to dockerize the Xposome application. The Xposome application has two parts: the portal page and the moderator page. In the moderator page, we have an option that allow users to retrieve their passwords if they forgot their login credentials.
Here are five steps to set-up Postfix and send new passwords to users:
Depending on your OS, for mine, I have CentOS 8 server. Therefore, I use this documenation on how to install and configure postfix mail server on CentOS 8. You can see this link to install and configure Postfix on Ubuntu 16.04.
See my previous post here
After Postfix is installed, the main configuration files is stored at /etc/postfix/main.cf.
To configure Postfix to listen to requests from docker container, there is something called docker bridge (docker0) which acts as a bridge between the ethernet port and the docker container so that data can go back and forth. Hence, we want to listen on the docker0 interface. To do that, type ifconfig on your host system to find out the bridge address and set your Postfix to listen on it.

As you can see in the image above, the IP address is 172.17.0.1
In the Postfix configuration file, set
inet_interfaces = 172.17.0.1
While you are there, add your actual docker container ip address to mynetworks
mynetworks = 172.17.0.5
172.17.0.5 is the private IP address of my docker container from which I use to send emails.
You can find the IP address of your docker container by
docker inspect [container_id/container_name]
For example,
docker inspect Xposome
Here is an email function that sends a temporary password to users who forgot their login credentials:
sendpassword <- function(from_sender="rchau88@bu.edu", to_recipient="lilychau999@gmail.com", recipient_first="Reina", recipient_last="Chau", recipient_account="Reina", tmp_pwd){
recipient=paste(recipient_first, recipient_last)
msg <- mime_part(
paste0(
'<!DOCTYPE>',
'<html>',
'<head>',
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
'<meta name="viewport" content="width=device-width, initial-scale=1.0"/>',
'<title>HTML MESSAGE</title>',
'<style type="text/css">',
'</style>',
'</head>',
'<body>',
'<p>Hi <strong>', recipient_first, ',</strong></p>',
'<p>The password for your Xposome account has changed.</p>',
'<p></p>',
'<p>Username: <strong>', recipient_account, '</strong></p>',
'<p>Temporary password: <strong>', tmp_pwd, '</strong></p>',
'<br>',
'<p>Best,</p>',
'<p>Montilab Team</p>',
'</body>',
'</html>'
)
)
## Override content type.
msg[["headers"]][["Content-Type"]] <- "text/html"
from <- paste0("\"Montilab Team\"<", from_sender, ">")
to <- paste0("\"", recipient, "\"<", to_recipient, ">", collapse="")
subject <- "Temporary password for Xposome"
body <- list(msg)
sendmail(from, to, subject, body, control=list(smtpServer="smtp.bu.edu", smtpPort="25"))
}
On the shiny interface, when a user clicks the forgot password? link, a modal dialog will pop up. After he/she provided the user account information and click the submit button, a new temporary password will be send to his/her email.
observeEvent(input$FG_Button, {
Firstname=trimws(input$FG_Firstname);
Lastname=trimws(input$FG_Lastname);
Username=trimws(input$FG_Username);
login_dat <- read.csv(paste0("data/User_Login_List.csv"), header = TRUE, stringsAsFactors = FALSE)
if(Firstname=="" | Lastname=="" | Username==""){
forgotpasswordwarningmsg("Please fill in the required (*) fields.")
}else{
row <- which(login_dat$Username == Username)
if(length(row) > 0){
tmp_pwd <- password(n = 10, numbers = TRUE, case = TRUE, special = c("?", "!", "&", "%", "$"))
login_dat$Password[row[1]] <- sodium::password_store(as.character(tmp_pwd))
sendpassword(
from_sender="rchau88@bu.edu",
to_recipient="lilychau999@gmail.com",
recipient_first=Firstname,
recipient_last=Lastname,
recipient_account=Username,
tmp_pwd=tmp_pwd
)
write.csv(login_dat, paste0("data/User_Login_List.csv"), row.names = FALSE)
forgotpasswordwarningmsg("Thank you for your submission! A temporary password has been sent to your email.")
}else{
forgotpasswordwarningmsg("This username does not exist in our database. Please enter another username.")
}
}
})
Here a snapshoot of the Xposome interface:

if you are using the same email format that I have structured, then you should receive an email similar to this one.

Developed by Reina Chau, Stefano Monti.
Site built with flexdashboard 0.5.2
The API Explorer provides documentation on how users can retrieve data from Xposome application and create their next project. All successful responses are returned in JSON. Only queries that respond with a 200 response code is considered successful.
Here are standard HTTP codes you will find in the “Status” element of the response body.
| Status Code | Description |
| 200 OK | Standard HTTP successful response |
| 404 Bad Request or Source Not Found. | Standard HTTP invalid request response |
| 500 Internal Server Error | There was an unexpected error on our server. If you see this please contact montilab@bu.edu |
There are four data sources that are returned from the API.
| Data | Description | Data Type |
| Portals | a list of portals in the GeneHive Database | list |
| Chemicals | a list of chemicals available in a specific portal | list |
| Statistics | a summary statistic for a specific chemical based on gene expression, gene set enrichment analysis and gene connectivity | data frame |
| Datasets | provide the data source for the Xposome portals such as profile and chemical annotation, expression set, gene set enrichent, etc. | data frame |
Below are a list of R packages and GET requests in R to retrieve data from Xposome portals. However, one can use Python or other programs to implement the REST API calls.
# R packages required for API calls
library(jsonlite)
library(httr)
| /portals | Return a list of portals available in the GeneHive database |
https://montilab.bu.edu/Xposome-API/portals?orderby=asc
| Parameter | Value | Description | Data Type |
| orderby | asc (default) or desc | Sort options | string |
# url for local testing
url <- "https://montilab.bu.edu/Xposome-API/portals?orderby=asc"
# Send GET Request to API
res <- GET(url = url, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
### If GET request is successful, return the results
if(test_request == "pass"){
portals <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(portals))
}
| /chemicals | Return a list of chemicals available in a specific portal |
https://montilab.bu.edu/Xposome-API/chemicals?portal=MCF10A
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
portal_name = "MCF10A"
# url for local testing
url0 <- paste0("https://montilab.bu.edu/Xposome-API/chemicals?portal=", portal_name)
# Send GET Request to API
res <- GET(url = url0, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
chemicals <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(chemicals))
}
| /gene_expression | Return a collection of differential expressed genes that exposed to a known toxin |
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
| chemical_id | 1,2-Dibromo-3-chloropropane or … | Name of the chemicals or CAS ids of a chemical in a specific portal | string |
| summarize.func | median (default) or mean/min/max/Q1/Q3 | Name of the summarize functions | string |
| landmark | TRUE or FALSE (default) | Whether to include landmark genes | boolean |
| do.nmarkers | TRUE (default) or FALSE | Whether to filter by the number of landmark genes (ranging from 1 to 1000) | boolean |
| do.scorecutoff | TRUE (default) or FALSE | Whether to filter by the z-scores (cutoff from -2 to 2) | boolean |
chemical_id = chemicals[4]
# url for local testing
url1 <- paste0("https://montilab.bu.edu/Xposome-API/gene_expression?portal=", portal_name, "&chemical_id=", chemical_id, "&summarize.func=median&landmark=TRUE&do.markers=TRUE&do.scorecutoff=TRUE")
# Send GET Request to API
res <- GET(url = url1, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
gene_expression_stat <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(gene_expression_stat))
}
| /gs_enrichment | Return a collection of gene set enrichment that was exposed to a given drug |
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
| chemical_id | 1,2-Dibromo-3-chloropropane or … | Name of the chemicals or CAS ids of a chemical in a specific portal | string |
| geneset | Hallmark (default) or C2 or NURSA | Collection of the gene set enrichment | string |
| gsva | gsva (default) | Method of the gene set enrichment analysis | string |
| summarize.func | median (default) or mean/min/max/Q1/Q3 | Name of the summarize functions | string |
# url for local testing
url2 <- paste0("https://montilab.bu.edu/Xposome-API/gs_enrichment?portal=", portal_name, "&chemical_id=", chemical_id, "&geneset=Hallmark&gsva=gsva&summarize.func=median")
# Send GET Request to API
res <- GET(url = url2, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
gs_enrichment_stat <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(gs_enrichment_stat))
}
| /connectivity | Return a collection of human diseases that are linked to exposure of a known chemical |
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
| chemical_id | 1,2-Dibromo-3-chloropropane or … | Name of the chemicals or CAS ids of a chemical in a specific portal | string |
| connectivity_classes | pcl (Perturbagen Classes, default) or pert (Perturbagens) | Name of the connectivity classes | string |
| summarize.func | median (default) or mean/min/max/Q1/Q3 | Name of the summarize functions | string |
# url for local testing
url3 <- paste0("https://montilab.bu.edu/Xposome-API/connectivity?portal=", portal_name, "&chemical_id=", chemical_id, "&connectivity_classes=pcl&summarize.func=median")
# Send GET Request to API
res <- GET(url = url3, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
connectivity_stat <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(connectivity_stat))
}
| /profile_annotation | Return the profile annotation of a specific portal |
https://montilab.bu.edu/Xposome-API/profile_annotation?portal=MCF10A
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
# url for local testing
url4 <- paste0("https://montilab.bu.edu/Xposome-API/profile_annotation?portal=", portal_name)
# Send GET Request to API
res <- GET(url = url4, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
profile_annotation <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(profile_annotation))
}
| /chemical_annotation | Return the chemical annotation of of a specific portal |
https://montilab.bu.edu/Xposome-API/chemical_annotation?portal=MCF10A
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
# url for local testing
url5 <- paste0("https://montilab.bu.edu/Xposome-API/chemical_annotation?portal=", portal_name)
# Send GET Request to API
res <- GET(url = url5, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
chemical_annotation <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(chemical_annotation))
}
| /expression_set | Return the gene expression dataset of a specific portal |
https://montilab.bu.edu/Xposome-API/expression_set?portal=MCF10A
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
# url for local testing
url6 <- paste0("https://montilab.bu.edu/Xposome-API/expression_set?portal=", portal_name)
# Send GET Request to API
res <- GET(url = url6, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
expression_set <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(expression_set))
}
| /enrichment_set | Return the enrichment dataset of a specific portal |
https://montilab.bu.edu/Xposome-API/enrichment_set?portal=MCF10A&geneset=Hallmark&gsva=gsva
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
| geneset | Hallmark (default) or C2 or NURSA | Collection of the gene set enrichment | string |
| gsva | gsva (default) | Method of gene set enrichment analysis | string |
# url for local testing
url7 <- paste0("https://montilab.bu.edu/Xposome-API/enrichment_set?portal=", portal_name, "&geneset=Hallmark&gsva=gsva")
# Send GET Request to API
res <- GET(url = url7, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
enrichment_set <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(enrichment_set))
}
| /connectivity_set | Return the connectivity dataset of a specific portal |
https://montilab.bu.edu/Xposome-API/connectivity_set?portal=MCF10A&connectivity_classes=pcl
| Parameter | Value | Description | Data Type |
| portal | MCF10A or HEPG2 or … | Name of the portals | string |
| connectivity_classes | pcl (Perturbagen Classes, default) or pert (Perturbagens) | Name of the connectivity classes | string |
# url for local testing
url8 <- paste0("https://montilab.bu.edu/Xposome-API/connectivity_map?portal=", portal_name, "&connectivity_classes=pcl")
# Send GET Request to API
res <- GET(url = url8, encode = 'json')
# Check the status of GET request
test_request <- tryCatch({
stop_for_status(res)
"pass"
}, error = function(e) {
"fail"
})
# If GET request is successful, return the results
if(test_request == "pass"){
connectivity_map <- fromJSON(fromJSON(rawToChar(res$content)))
print(head(connectivity_map))
}
Developed by Reina Chau, Stefano Monti.
Site built with flexdashboard 0.5.2